using System;
using System.Linq.Expressions;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.Mapper
{
    public class GridPropertyInfo
    {
        public readonly Type PropertyType;
        public readonly Type ViewModelPropertyType;
        public readonly string PropertyPath;
        public readonly LambdaExpression EntityProperty;
        public readonly LambdaExpression ViewModelProperty;
        public readonly LambdaExpression CustomExpression;
        public bool AcceptNullValuesWhenFiltering { get; internal set; }

        // ReSharper disable UnusedMember.Local
        private GridPropertyInfo() { }
        // ReSharper restore UnusedMember.Local

        public GridPropertyInfo(Type propertyType, string propertyPath)
        {
            Contract.Expect(() => propertyType).IsNotNull();
            Guard.IsNotNullOrEmpty(() => propertyPath);

            PropertyType = propertyType;
            PropertyPath = propertyPath;
        }

        public GridPropertyInfo(LambdaExpression entityProperty, LambdaExpression viewModelProperty, Type entityPropertyType, Type viewModelPropertyType)
        {
            if (IsValidPropertyExpression(entityProperty))
            {
                EntityProperty = entityProperty;
                PropertyPath = entityProperty.GetPropertyPath();
            }
            else
            {
                CustomExpression = entityProperty;
            }

            PropertyType = entityPropertyType;
            ViewModelPropertyType = viewModelPropertyType;

            if (IsValidPropertyExpression(viewModelProperty) == false)
                throw new NotSupportedException(string.Format("This expression is not supported: {0}", viewModelProperty));

            ViewModelProperty = viewModelProperty;
        }

        private static bool IsValidPropertyExpression(LambdaExpression expression)
        {
            Contract.Expect(() => expression).IsNotNull();
            Contract.Expect(() => expression.NodeType)
                .Meet(expressionType => expressionType == ExpressionType.Lambda);

            var body = expression.Body;

            if (body.NodeType == ExpressionType.Convert)
            {
                body = ((UnaryExpression)body).Operand;
            }

            return body.NodeType == ExpressionType.MemberAccess;
        }

        public override string ToString()
        {
            return string.Format("Path: {0}, Type: {1}", PropertyPath ?? CustomExpression.ToString(), PropertyType.FullName);
        }

        public bool IsArtificial()
        {
            return ViewModelPropertyType == null;
        }
    }
}